package com.aerospace.example.grpc.java;

import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.Message;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @ClassName RouteGuideClient
 * @Description
 * @Author yuhang11
 * @Date 2021/7/8 10:48 上午
 * @Version 1.0
 */
@Slf4j
public class RouteGuideClient {
    private final RouteGuideGrpc.RouteGuideBlockingStub blockingStub;
    private final RouteGuideGrpc.RouteGuideStub asyncStub;

    private Random random = new Random();
    private TestHelper testHelper;

    public RouteGuideClient(Channel channel){
        blockingStub = RouteGuideGrpc.newBlockingStub(channel);
        asyncStub = RouteGuideGrpc.newStub(channel);
    }

    public static void main(String[] args) throws InterruptedException {
        String target = "localhost:8980";
        if (args.length > 0){
            if ("--help".equals(args[0])){
                log.error("Usage:[target]");
                log.error("target The server to connect to. Defaults to" + target);
                System.exit(1);
            }
            target = args[0];
        }

        List<Feature> features;
        try{
            features = RouteGuideUtil.parseFeatures(RouteGuideUtil.getDefaultFeaturesFile());
        }catch (Exception e){
            e.printStackTrace();
            return;
        }

        ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
        try{
            RouteGuideClient client = new RouteGuideClient(channel);
            //查找一个无效的特征
            client.getFeature(40000,20000);
            client.getFeature(0,0);
            // Looking for features between 40, -75 and 42, -73.
            client.listFeatures(400000000, -750000000, 420000000, -730000000);
            // Record a few randomly selected points from the features file.
            client.recordRoute(features, 10);
            // Send and receive some notes.
            CountDownLatch finishLatch = client.routeChat();

            if (!finishLatch.await(1, TimeUnit.MINUTES)) {
                log.info("routeChat can not finish within 1 minutes");
            }
        }finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }


    }


    public void getFeature(int lat, int lon){
        log.info("*** GetFeature: lat={o} lon={1}", lat, lon);
        Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();
        Feature feature;
        try{
            feature = blockingStub.getFeature(request);
            if (testHelper != null){
                testHelper.onMessage(feature);
            }
        }catch(Exception e){
            log.warn("RPC failed:{0}", e.getMessage());
            if (testHelper != null){
                testHelper.onRpcError(e);
            }
            return;
        }
        if (RouteGuideUtil.exists(feature)){
            log.info("Found feature called \"{0}\" at {1},{2}",
                    feature.getName(),
                    RouteGuideUtil.getLatitude(feature.getLocation()),
                    RouteGuideUtil.getLongitude(feature.getLocation()));
        }else {
            log.info("Found no feature at {0},{1}");
            RouteGuideUtil.getLatitude(feature.getLocation());
            RouteGuideUtil.getLongitude(feature.getLocation());
        }
    }

    public void listFeatures(int lowLat, int lowLon, int hiLat, int hiLon){
        log.info("*** ListFeatures: lowLat={0} lowLon={1} hiLat={2} hiLon={3}",
                lowLat, lowLat, hiLat, hiLon);
        Rectangle request = Rectangle.newBuilder()
                .setLo(Point.newBuilder().setLatitude(lowLat).setLongitude(lowLon).build())
                .setHi(Point.newBuilder().setLatitude(hiLat).setLongitude(hiLon).build()).build();
        Iterator<Feature> features;
        try{
            features = blockingStub.listFeatures(request);
            for (int i = 1; features.hasNext();i++){
                Feature feature = features.next();
                log.info("Result #"+i+":{0}",feature);
                if (testHelper != null){
                    testHelper.onMessage(feature);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
            if (testHelper != null){
                testHelper.onRpcError(e);
            }
        }
    }

    public void recordRoute(List<Feature> features, int numPoints) throws InterruptedException {
        log.info("*** RecordRoute");
        final CountDownLatch finishLatch = new CountDownLatch(1);
        StreamObserver<RouteSummary> responseObserver = new StreamObserver<RouteSummary>() {
            @Override
            public void onNext(RouteSummary routeSummary) {
                log.info("Finish trip with {0} points.Passed {1} features. " +
                        "Travelled{2} meters.It took {3} seconds.", routeSummary.getPointCount(),
                        routeSummary.getFeatureCount(), routeSummary.getDistance(), routeSummary.getElapsedTime());
                if (testHelper != null){
                    testHelper.onMessage(routeSummary);
                }
            }

            @Override
            public void onError(Throwable throwable) {
                log.warn("RecordRoute Failed: {0}", Status.fromThrowable(throwable));
                if (testHelper != null){
                    testHelper.onRpcError(throwable);
                }
                finishLatch.countDown();
            }

            @Override
            public void onCompleted() {
                log.info("Finished RecordRoute");
                finishLatch.countDown();
            }
        };
        StreamObserver<Point> requestObserver = asyncStub.recordRoute(responseObserver);
        try{
            for (int i = 0; i<numPoints; i++){
                int index = random.nextInt(features.size());
                Point point = features.get(index).getLocation();
                log.info("Visiting point {0} {1}", RouteGuideUtil.getLatitude(point),
                        RouteGuideUtil.getLongitude(point));
                requestObserver.onNext(point);
                Thread.sleep(random.nextInt(1000) + 500);
                if(finishLatch.getCount() == 0){
                    return;
                }
            }
        }catch (Exception e){
            requestObserver.onError(e);
            e.printStackTrace();
        }
        requestObserver.onCompleted();
        if (!finishLatch.await(1, TimeUnit.MINUTES)){
            log.warn("recordRoute can not finish within 1 minutes");
        }
    }

    public CountDownLatch routeChat(){
        log.info("*** RouteChat");
        final CountDownLatch finishLatch = new CountDownLatch(1);
        StreamObserver<RouteNote> requestObserver =
                asyncStub.routeChat(new StreamObserver<RouteNote>() {
                    @Override
                    public void onNext(RouteNote routeNote) {
                        log.info("Got message \"{0}\" at {1}, {2}", routeNote.getMessage(), routeNote.getLocation()
                                .getLatitude(), routeNote.getLocation().getLongitude());
                        if (testHelper != null){
                            testHelper.onMessage(routeNote);
                        }
                    }

                    @Override
                    public void onError(Throwable throwable) {
                        log.warn("RouteChat Failed: {0}", Status.fromThrowable(throwable));
                        if (testHelper != null){
                            testHelper.onRpcError(throwable);
                        }
                        finishLatch.countDown();
                    }

                    @Override
                    public void onCompleted() {
                        log.info("Finished RouteChat");
                        finishLatch.countDown();
                    }
                });
        try{
            RouteNote[] requests = {
                    newNote("First message",0,0),
                    newNote("Second message", 0, 10_000_000),
                    newNote("Third message",10_000_000,0),
                    newNote("Fourth message", 10_000_000,10_000_000)
            };
            for (RouteNote request:requests){
                log.info("Sending message \"{0}\" at {1},{2}",
                        request.getMessage(),
                        request.getLocation().getLatitude(),
                        request.getLocation().getLongitude());
                requestObserver.onNext(request);
            }
        }catch (Exception e){
            requestObserver.onError(e);
            e.printStackTrace();
        }
        requestObserver.onCompleted();
        return finishLatch;
    }

    private RouteNote newNote(String message, int lat, int lon) {
        return RouteNote.newBuilder().setMessage(message)
                .setLocation(Point.newBuilder().setLatitude(lat).setLongitude(lon).build()).build();
    }

    @VisibleForTesting
    interface TestHelper{
        void onMessage(Message message);
        void onRpcError(Throwable exception);
    }

    @VisibleForTesting
    void setTestHelper(TestHelper testHelper){
        this.testHelper = testHelper;
    }
}
